from jqdata import log
from jqdata import get_current_data
from securities_info import * 
import math

"""
|||聚宽测试从以下复制
"""

import pandas as pd

""" 
实现书上的海龟系统
@author Channing Hu
"""
class TurtleSystem(object):

    # 系统1所配金额占总金额比例
    sys1_ratio = 0.5

    """
    系统运行时间
    与计算N值相关
    如果是期货，跟合约日期相关，如果使用主力合约计算则是全局变量
    # Record the number of days for this trading system
    """
    days = 0

    """
    初始化
    """
    def __init__(self):
        
        self.sys1 = Sys1TradeSystem('sys1')
        self.sys1.pindex = 0
        self.sys2 = BaseTradeSystem('sys2')
        self.sys2.pindex = 1
        self.sec_info = SecuritiesInfo()

        self.sub_syses = [
            self.sys1,
            self.sys2
        ]

    """  """
    def set_sub_port(self, portfolio):
        if len(self.sub_syses) != len(portfolio.subportfolios):
            raise ValueError("子账户数不匹配")

        i = 0
        for sub_sys in self.sub_syses:
            sub_sys.portfolio = portfolio.subportfolios[i]
            i += 1

    
    """
    每日执行，盘前
    更新各证券需要的数据: 
    N值
    过去收盘价历史
    """
    def refresh_basic_data(self, portfolio, securities_data_history):
        #移除不需要的数据
        securities = list(self.sec_info.data.keys())
        for sec in securities:
            if sec not in securities_data_history.keys():
                self.sec_info.data.pop(sec)

        for sec in securities_data_history:
            # prices = securities_data_history[sec]['prices']
            sdh = securities_data_history[sec]
            # 初始设置一次
            if sec not in self.sec_info.data.keys():
                self.sec_info.data[sec] = {}
                self.sec_info.data[sec]['type'] = sdh['type']
                if self.sec_info.sec_is_future(sec):
                    self.sec_info.data[sec]['end_date'] = sdh['end_date']
            
            # 每日更新
            self.sec_info.data[sec]['prices'] = sdh['prices']
            if self.sec_info.sec_is_future(sec):
                self.sec_info.data[sec]['main_contract'] = sdh['main_contract']

            # 本日涨跌停缓存初始化
            self.sec_info.data[sec]['high_limit'] = None
            self.sec_info.data[sec]['low_limit'] = None
            
            # 对于股票是每手股票数
            self.sec_info.data[sec]['dollars_per_share'] = self.sec_info.get_value_price_multiplier(sec)
            # 证券可做空，简单判断，TODO 更好的判断
            if self.sec_info.sec_is_future(sec):
                self.sec_info.data[sec]['shortable'] = True
            else:
                self.sec_info.data[sec]['shortable'] = False

        self.sec_info.update_Ns()
        for sys in self.sub_syses:
            sys.sec_info = self.sec_info
            sys.update_sec_trade_info(portfolio.subportfolios[sys.pindex], securities_data_history)
            # security = security_data.symbol

    
    def securities_info_to_refresh(self):
        """
        返回需要更新数据的证券代码
        """
        
        securities = []
        securities.extend(self.sys1.securities_info_to_refresh())
        securities.extend(self.sys2.securities_info_to_refresh())

        return securities

    
    def trade_strategy(self, portfolio, securities_data, current_price_attr = 'last_price'):
        """
        交易策略
        每分钟运行
        """
        
        sis = self.sec_info.data
        for sec in sis:
            if current_price_attr == 'price':
                sis[sec]['current_price'] = securities_data[sec].price
            else:
                sis[sec]['current_price'] = securities_data[sec].last_price

            if sis[sec]['high_limit'] is None:
                sis[sec]['high_limit'] = securities_data[sec].high_limit
            if sis[sec]['low_limit'] is None:
                sis[sec]['low_limit'] = securities_data[sec].low_limit  

        orders = []
        """
        [sec, -position, 'sell_for_cash', sec_info_data[sec]['current_price'], self.pindex]
        """
        orders1 = self.sys1.run(portfolio.subportfolios[0], self.sec_info.data)
        orders2 = self.sys2.run(portfolio.subportfolios[1], self.sec_info.data)
        
        if orders1 is not None:
            orders = orders1
        if orders2 is not None:
            orders.extend(orders2)

        return orders

    """ 
    订单成功成交
    UserOrder({'order_id': 1648633269, 'security': '601898.XSHG', 'amount': 3200, 'filled': 3200, 'price': 8.03, 'status': held, 'add_time': datetime.datetime(2022, 3, 7, 9, 31), 'side': 'long', 'action': 'open', 'pindex': 1, 'style': MarketOrderStyle: _limit_price=0.0})
    """
    def after_order_success(self, _order, order_type):
        if _order.pindex == 0:
            self.sys1.after_order_success(_order, order_type)
        elif _order.pindex == 1:
            self.sys2.after_order_success(_order, order_type)


    """
    需要的最长历史行情天数
    """
    def get_max_prices_days(self):
        return max(self.sys1.trade_config['enter_days2'], self.sys2.trade_config['enter_days'], self.sec_info.number_days)


    """
    检查持仓
    """
    def position_check(self, portfolio, security = None):
        for sys in self.sub_syses:
            pindex = sys.pindex
            p_sys = portfolio.subportfolios[pindex]

            tmp_set = set(p_sys.long_positions.keys()).union(sys.securities_trade_info.keys())
            # 不检查 货币基金持仓，TODO 
            if sys.money_market_fund['symbol'] in tmp_set:
                tmp_set.remove(sys.money_market_fund['symbol'])
            to_check_list = list(tmp_set)

            for sec in to_check_list:
                sys_positions = 0
                if sec in sys.securities_trade_info.keys():
                    sys_positions = sys.securities_trade_info[sec]['position']
                # 针对A股
                p_position = 0
                if sec in p_sys.long_positions.keys():
                    p_position = p_sys.long_positions[sec].total_amount + p_sys.long_positions[sec].locked_amount
                if sys_positions != p_position:
                    log.warning(sys.sys_id + " " + sec + " 持仓差异：应为 " + str(sys_positions) + " 实际 " + str(p_position))
                    """ 出现配股等情况时 TODO """
                    sys.securities_trade_info[sec]['position'] = p_position
                    log.info("已调整")

    
    def futures_contracts_rollover(self):
        """ 期货合约移仓 """
        orders = []
        for sys in self.sub_syses:
            orders.extend(sys.futures_contracts_rollover())

        return orders


    def manage_cash(self, portfolio, securities_data, current_price_attr = 'last_price'):
        """ 现金管理 """

        orders = []
        for sys in self.sub_syses:
            sub_portfolio = portfolio.subportfolios[sys.pindex]
            orders.extend(sys.manage_cash( sub_portfolio, securities_data, current_price_attr))
        
        return orders

class BaseTradeSystem():
    """
    海龟简单子系统类
    """

    # 单个合约最大允许单元
    unit_limit = 4

    # 平均真实波动对净资产变化，计算unit使用
    volatility_by_value = 0.01

    sec_info = SecuritiesInfo()

    money_market_fund = {
        'symbol':'511880.XSHG', 
        'position': 0
    }

    
    def __init__(self, sys_id = ''):
        """ 初始化 """

        """
        证券交易的相关信息
        sys1:
        {
            '000063.XSHE': {
                # 系统交易单元
                # 海龟控制头寸的减少计算量的方式，持仓总量控制在24 unit 以内，便于速算
                # 对于股票单位是股数，为了股票买入限制，应为整手，对于A股至少为100，
                # 对于期货至少为1
                'unit': 100,

                # 系统 开仓价
                'break_price' : 0,

                # 系统 仓位
                'position' : 0,

                # 当日开仓，A股股票仓位特有字段
                'T_position': 0
            }
        }
        """
        self.securities_trade_info = {}
        # 对于海龟系统可能同时运行 sys1 sys2
        self.sys_id = sys_id
        self.set_trade_config()
        pass
    
    
    def can_trade_both_side(self):
        """
        账户是否支持双向交易
        type: 可操作标的的类型，'stock' / 'index_futures' / 'futures' / 'stock_margin' / ‘open_fund', 
            其中 stock 包括股票和基金，index_futures 指金融期货，futures 包含股指期货和商品期货，
            stock_margin 为融资融券账户, open_fund为场外基金账户。
        """

        if self.portfolio.type in ['stock']:
            return False
        elif self.portfolio.type in ['stock_margin', 'index_futures', 'futures']:
            return True


    #设置海龟交易策略参数
    def set_trade_config(self):
        """ 
        入场离场信号参数，跟 Donchian 通道相关
        """
        self.trade_config = {
            # 系统2入市的trailing date
            'enter_days': 55,
            # 系统2 exiting market trailing date
            'exit_days': 20,
        }
    

    def securities_info_to_refresh(self):
        securities = []
        for sec in self.securities_trade_info:
            if self.securities_trade_info[sec]['position'] > 0:
                securities.append(sec)
            if self.securities_trade_info[sec]['short_position'] > 0:
                securities.append(sec)

        return securities
    
    #6
    # 入市：决定系统1、系统2是否应该入市，更新系统1和系统2的突破价格
    # 海龟将所有资金分为2部分：一部分资金按系统1执行，一部分资金按系统2执行
    # 输入：当前价格-float, 现金-float, 天数-int
    # 输出：none
    def market_in(self, security, current_price):
        signal = None

        sti = self.securities_trade_info[security]
        if sti['in_buy_list'] == False:
            return None

        si = self.sec_info.data[security]
        enter_days = self.trade_config['enter_days']

        # TODO 涨跌停是否发出买单
        if (current_price > sti['price_highest_'+str(enter_days)]) and (current_price < si['high_limit']):
            signal = [security, sti['unit'], 'market_in', current_price, 'long']

        # 证券允许做空
        if si['shortable'] == True:
            # 向下突破
            if (current_price < sti['price_lowest_'+str(enter_days)]) and (current_price > si['low_limit']):
                signal = [security, sti['unit'], 'market_in', current_price, 'short']

        return signal


    order_types = {
        'market_in':        '入场',
        'market_add':       '加仓',
        'market_out':       '离场',
        'stop_loss':        '止损',
        'sell_for_cash':    '调仓卖出'
    }
    """
    订单成功成交后
    """
    def after_order_success(self, _order, order_type):
        
        security = _order.security
        current_price = _order.price
        amount = _order.filled       #正数，已成交部分
        # 货币基金
        order_desc = ''
        if security == self.money_market_fund['symbol']:
            if _order.action == 'open':
                self.money_market_fund['position'] += amount
                order_desc = '买入'
            elif _order.action == 'close':
                self.money_market_fund['position'] -= amount
                order_desc = '卖出'

            msg = self.sys_id + ' ' + order_desc +'（'+_order.side+_order.action +\
                '）' + str(security) + str(amount) + ' @' + str(current_price)
            log.info(msg)
            return


        # 其他证券
        sti = self.securities_trade_info[security]

        position_name = 'position'
        if _order.side == 'short':
            position_name = 'short_position'

        if order_type == 'market_in':
            sti['break_price'] = current_price
            self.position_add(security, amount, _order.side)
        elif order_type == 'market_add':
            sti['break_price'] = current_price
            self.position_add(security, amount, _order.side)
        elif order_type == 'market_out':
            sti[position_name] -= amount
        elif order_type == 'stop_loss':
            sti[position_name] -= amount
        elif order_type == 'sell_for_cash':
            # 调仓卖出
            sti[position_name] -= amount
        elif order_type == 'close4_contract_rollover':
            # 调仓卖出
            sti[position_name] -= amount

        
        if order_type in self.order_types.keys():
            order_desc = self.order_types[order_type]

        msg = self.sys_id + ' ' + order_desc +'（'+_order.side+_order.action +\
            '）' + str(security) + str(amount) + ' @' + str(current_price)
        log.info(msg)

    
    """ 仓位记录 """
    def position_add(self, security, amount, side = 'long'):
        sti = self.securities_trade_info[security]
        if side == 'long':
            sti['position'] += amount
            if self.sec_info.sec_is_T1(security):
                sti['T_position'] += amount

        elif side == 'short':
            sti['short_position'] += amount

    
    """ 可出售仓位 """
    def position_saleable(self, security, position_type = 'long'):
        sti = self.securities_trade_info[security]
        if position_type == 'long':
            if self.sec_info.sec_is_A(security):
                return sti['position'] - sti['T_position']
            else:
                return sti['position']

        if position_type == 'short':
            return sti['short_position']

        raise ValueError("参数错误")

    # 加仓函数
    # 输入：当前价格-float, 现金-float, 天数-int
    # 输出：none
    def market_add(self, security, current_price):
        signal = None

        sti = self.securities_trade_info[security]
        if sti['in_buy_list'] == False:
            return None

        si = self.sec_info.data[security]

        break_price = sti['break_price']
        # 每上涨0.5N，加仓一个单元
        #未到涨停，且 多仓 > 0
        if (current_price < si['high_limit']) and (sti['position'] > 0):  
            if current_price >= break_price + 0.5*(si['Ns'])[-1]: 
                # 仓位未到上限 - 1 本系统交易单位
                if sti['position'] <= int((self.unit_limit - 1) * sti['unit']):
                    signal = [security, sti['unit'], 'market_add', current_price, 'long']

        if (current_price > si['low_limit']) and (sti['short_position'] > 0):
            if current_price <= break_price - 0.5*(si['Ns'])[-1]: 
                if sti['short_position'] <= int((self.unit_limit - 1) * sti['unit']):
                    signal = [security, sti['unit'], 'market_add', current_price, 'short']
        return signal

    
    """
    # 离场函数
    # 输入：当前价格-float, 天数-int
    """    
    def market_out(self, security, current_price):
        signal = None
        sti = self.securities_trade_info[security]
        si = self.sec_info.data[security]

        exit_days = self.trade_config['exit_days']
        # Function for leaving the market
        # 若当前价格低于前out_date天的收盘价的最小值, 则卖掉所有持仓
        position_saleable = self.position_saleable(security, 'long')
        if position_saleable > 0:
            if (current_price < sti['price_lowest_'+str(exit_days)]) and (current_price > si['low_limit']) :
                signal = [security, -position_saleable, 'market_out', current_price, 'long']
        
        position_saleable = self.position_saleable(security, 'short')
        if position_saleable > 0:
            if (current_price > sti['price_highest_'+str(exit_days)]) and (current_price < si['high_limit']): 
                signal = [security, -position_saleable, 'market_out', current_price, 'short']

        return signal
    
    
    """
    # 止损函数
    # 输出：none
    """
    def stop_loss(self, security, current_price):
        signal = None
        # 损失大于2N，卖出股票
        si = self.sec_info.data[security]
        sti = self.securities_trade_info[security]
        
        break_price = sti['break_price']
        # If the price has decreased by 2N, then clear all position
        position_saleable = self.position_saleable(security, 'long')
        if position_saleable > 0:
            if (current_price < (break_price - 2*(si['Ns'])[-1])) and (current_price > si['low_limit']):
                signal = [security, -position_saleable, 'stop_loss', current_price, 'long']

        position_saleable = self.position_saleable(security, 'short')
        if position_saleable > 0:
            if (current_price > (break_price + 2*(si['Ns'])[-1])) and (current_price < si['high_limit']):
                signal = [security, -position_saleable, 'stop_loss', current_price, 'short']
        
        return signal


    def run(self, portfolio, sec_info_data):
        """
        海龟子系统执行
        """

        self.portfolio = portfolio
        """
        signal = [security, amount, 'market_in', current_price, 'long']
        """
        sell_signals, buy_signals = self.check_opportunity(sec_info_data)
        
        orders = []
        orders.extend(self.signals_2_orders(sell_signals))
        if portfolio.type == 'stock':   # A股非融资融券根据强弱调仓，可以满仓
            # 检查订单执行完成所需的资金是否足够 
            cash_available, cash_need = self.cash_check(portfolio, sell_signals, buy_signals)
            
            money_fund_value = self.money_market_fund['position'] * 100   # 大概价值
            # 钱很多，signal 转order返回, 额外的钱买货币基金
            if (cash_available + money_fund_value) >= cash_need:

                money_fund_shares = 0
                if (cash_available - cash_need) < 0:    # 现金少卖出货币基金
                    # 如果只是买基金的话，多卖一点，留冗余
                    money_fund_shares = math.floor((cash_available - cash_need - 1000) / 100 / 100 ) * 100
                    orders.append([self.money_market_fund['symbol'], money_fund_shares, '', 100, 'long', self.pindex])
                
                orders.extend(self.signals_2_orders(buy_signals))
                return orders
            """
            cash_available < 0 持仓调整
            order = [sec, -position, 'sell_for_cash', sec_info_data[sec]['current_price'], 'long', self.pindex]
            """
            pa_buy_orders, pa_sell_orders = self.position_adjustment_stock(buy_signals, sec_info_data, cash_available)
            
            orders.extend(pa_sell_orders)
            orders.extend(pa_buy_orders)
        elif portfolio.type in ['futures', 'index_futures']:
            # TODO: 仓位控制逻辑，优先根据海龟控制法控制仓位
            if(len(buy_signals) == 0):
                return orders

            orders.extend(self.prepare_orders_future(buy_signals))
        return orders
    
    """检查资金是否够"""
    def cash_check(self, portfolio, sell_signals, buy_signals):
        
        cash_available = portfolio.cash
        cash_need = 0
        for signal in sell_signals:
            sec = signal[0]
            position = signal[1]     #卖出为负数
            price = signal[3]
            sec_margin_rate = self.sec_info.sec_margin_rate(sec)
            cash_available -= position * price * sec_margin_rate

        for signal in buy_signals:
            sec = signal[0]
            position = signal[1]     #买入为正数
            price = signal[3]
            sec_margin_rate = self.sec_info.sec_margin_rate(sec)
            cash_need += position * price * sec_margin_rate
        
        return cash_available, cash_need

    
    """ 信号转委托 """
    def signals_2_orders(self, signals):
        orders = []
        for i in range(len(signals)):
            s = signals[i]
            s.append(self.pindex)
            orders.append(s)

        return orders

    """
    # 所有持仓和待购买的股票过去x日涨幅排序，判断强弱 TODO 检查更好的方法
    # position_type：仓位类型，0:已持仓；1:待购买
    # increase：最近x日涨幅
    """
    def position_adjustment_stock(self, buy_signals, sec_info_data, cash_available):
        # 构建数组，排序涨跌幅绝对值，
        enter_days = self.trade_config['enter_days']
        data_list = []
        #货币基金
        mf_position = self.money_market_fund['position']
        mf_value = mf_position * 100

        # 粗略估值
        cash_available_max = cash_available + mf_value
        for sec in self.securities_trade_info:
            si = sec_info_data[sec]
            position_saleable = self.position_saleable(sec)
            if si['low_limit'] >= si['current_price']:
                position_saleable = 0
            else:
                """股票是否停牌 TODO 调整调用方式 """
                d = get_current_data()
                if d[sec].paused:
                    position_saleable = 0

            if position_saleable <= 0:
                continue
            
            current_price = sec_info_data[sec]['current_price']
            price_before = sec_info_data[sec]['prices']['close'][-enter_days]
            increase = (current_price - price_before) / price_before
            value = position_saleable * current_price
            data_list.append([sec, position_saleable, 0, increase, value, ''])
            cash_available_max += value

        for o in buy_signals:
            sec = o[0]
            position = o[1]
            order_type = o[2]
            current_price = sec_info_data[sec]['current_price']
            price_before = sec_info_data[sec]['prices']['close'][-enter_days]
            increase = (current_price - price_before) / price_before
            data_list.append([sec, position, 1, increase, position * current_price, order_type])

        """ 根据回测 51、52 选涨幅高的并不一定好，待多验证 """
        data_list = sorted(data_list, key=lambda x: x[4], reverse=True)
        balance_i = len(data_list) - 1
        cash_need = 0
        cash_available2 = cash_available_max
        for i in range(len(data_list)):
            # 待购买
            d = data_list[i]
            position_type = d[2]
            value = d[4]
            if position_type == 1:          #买单
                cash_need += value
            else:                           #持仓可卖
                cash_available2 -= value

            if cash_available2 < cash_need:
                balance_i = i
                break
        
        pa_buy_orders = []
        pa_sell_orders = []

        # 优先卖出货币基金，价格按100粗略算
        mf_position_tosell = min(math.ceil((cash_need - cash_available) / 100) * 100,  mf_position)
        _order = [self.money_market_fund['symbol'], -mf_position_tosell, '', 100, 'long', self.pindex]
        pa_sell_orders.append(_order)

        for i in range(len(data_list)):
            # 重新生成买单
            r = data_list[i]
            position_type = r[2]
            position = r[1]
            value = r[4]
            sec = r[0]
            order_type = r[5]
            if i < balance_i and position_type == 1:
                _order = [sec, position, order_type, sec_info_data[sec]['current_price'], 'long', self.pindex]
                pa_buy_orders.append(_order)
                continue
            
            # 卖出
            if i >= balance_i and position_type == 0:
                _order = [sec, -position, 'sell_for_cash', sec_info_data[sec]['current_price'], 'long', self.pindex]
                pa_sell_orders.append(_order)
                pass

        return pa_buy_orders, pa_sell_orders

    # 单方向仓位限制
    strong_related_position_limit = 6
    weak_related_position_limit = 10
    one_side_position_limit = 12

    """期货订单准备，根据现有持仓以及新的买入信号"""
    def prepare_orders_future(self, buy_signals):
        orders = []
        portfolio = self.portfolio
        
        long_position_units_sum = 0
        short_position_units_sum = 0
        cash_need = 0
        for sec in self.securities_trade_info:
            sti = self.securities_trade_info[sec]
            long_position_units_sum += sti['position'] / sti['unit']
            short_position_units_sum += sti['short_position'] / sti['unit']

        for signal in buy_signals:
            sec = signal[0]
            position = signal[1]     #买入为正数
            price = signal[3]
            side = signal[4]
            if side == 'long':
                long_position_units_sum += position / sti['unit']
            elif side == 'short':
                short_position_units_sum += position / sti['unit']

            sec_margin_rate = self.sec_info.sec_margin_rate(sec)
            cash_need += position * price * sec_margin_rate

        # TODO: 资金检查，开仓之前检查是否有反方向持仓，优先平仓
        if long_position_units_sum <= self.one_side_position_limit and \
            short_position_units_sum <= self.one_side_position_limit:

            orders.extend(self.signals_2_orders(buy_signals))
        else:
            # TODO: position_adj
            log.warning("仓位重了")

        

        return orders
    """
    # 期货类调仓，首先根据海龟风控逻辑
    """
    def position_adjustment_future(self):
        
        pass

    """
    # 所有持仓和待购买的股票过去x日涨幅排序，判断强弱，仅处理新买单
    # position_type：仓位类型，0:已持仓；1:待购买
    # increase：最近x日涨幅
    """
    def position_adjustment2(self, buy_orders, sec_info_data, cash_available):
        in_date = self.trade_config['enter_days']
        data_list = []
        
        for o in buy_orders:
            sec = o[0]
            position = o[1]     #卖出为负数
            current_price = sec_info_data[sec]['current_price']
            price_before = sec_info_data[sec]['prices']['close'][-in_date]
            increase = (current_price - price_before) / price_before
            data_list.append([sec, position, 1, increase, position * current_price, o[2]])

        data = pd.DataFrame(data_list, columns=['security', 'position', 'position_type', 'increase', 'value', 'order_type'])
        data = data.sort_values(by = 'increase',ascending = False)
        
        balance_i = 0
        for i in range(len(data)):
            # 待购买
            d1 = data.iloc[:i+1]
            cash_need = d1[d1['position_type'] == 1]['value'].sum()

            # 持仓可卖
            d2 = data.iloc[i+1:]
            cash_available2 = d2[d2['position_type'] == 0]['value'].sum() + cash_available
            if cash_available2 < cash_need:
                balance_i = i
                break
        
        pa_buy_orders = []
        pa_sell_orders = []
        for i in range(len(data)):
            r = data.iloc[i]
            if i < balance_i and r['position_type'] == 1:
                sec = r['security']
                order = [sec, r['position'], r['order_type'], sec_info_data[sec]['current_price']]
                pa_buy_orders.append(order)
                continue
            
            if i >= balance_i and r['position_type'] == 0:
                sec = r['security']
                order = [sec, -r['position'], 'sell_for_cash', sec_info_data[sec]['current_price']]
                pa_sell_orders.append(order)
                pass

        return pa_buy_orders, pa_sell_orders

    """ 基本找到买卖点 """
    def check_opportunity(self, sec_info_data):
        # 系统1的操作
        buy_signals = []
        sell_signals = []

        for security in self.securities_trade_info:
            sti = self.securities_trade_info[security]
            current_price = sec_info_data[security]['current_price']

            if sti['position'] == 0 and sti['short_position'] == 0:
                signal = self.market_in(security, current_price)
                if signal is not None:
                    buy_signals.append(signal)
            else:
                signal = self.market_add(security, current_price)
                if signal is not None:
                    buy_signals.append(signal)
                
                # 少数情况下信号同时发出，优先止损，抛弃离场
                signal = self.stop_loss(security, current_price)
                if signal is not None:
                    sell_signals.append(signal)
                
                else:   
                    signal = self.market_out(security, current_price)
                    if signal is not None:
                        sell_signals.append(signal)
        
        return sell_signals, buy_signals

    """ 
    盘前运行一次
    更新本系统的各证券基本交易单位 
    """
    def update_sec_trade_info(self, portfolio, securities_data_history):
        # 计算波动的价格
        #移除不需要的数据
        securities = list(self.securities_trade_info.keys())
        for sec in securities:
            if sec not in securities_data_history.keys():
                self.securities_trade_info.pop(sec)

        enter_days = self.trade_config['enter_days']
        exit_days = self.trade_config['exit_days']
        
        sis = self.sec_info.data
        for sec in sis:
            if sec not in self.securities_trade_info.keys():
                self.securities_trade_info[sec] = {}

            sti = self.securities_trade_info[sec]
            sti['unit'] = self.calc_sec_sys_buy_unit(portfolio, sec)
            if 'position' not in sti.keys():
                sti['position'] = 0
            if 'short_position' not in sti.keys():
                sti['short_position'] = 0

            sti['price_highest_'+str(enter_days)] = max(sis[sec]['prices']['high'][-enter_days:])
            sti['price_lowest_'+str(enter_days)] = min(sis[sec]['prices']['high'][-enter_days:])
            sti['price_highest_'+str(exit_days)] = max(sis[sec]['prices']['low'][-exit_days:])
            sti['price_lowest_'+str(exit_days)] = min(sis[sec]['prices']['low'][-exit_days:])

            """每交易日前重置当日买入仓位"""
            sti['T_position'] = 0

        """ 是否可购买 """
        for sec in self.securities_trade_info:
            sti = self.securities_trade_info[sec]
            sti['in_buy_list'] = False
            if sec in securities_data_history.keys():
                if ('in_buy_list' in securities_data_history[sec].keys()) and securities_data_history[sec]['in_buy_list']:
                    sti['in_buy_list'] = True


    """ 计算证券基础交易单位 """
    def calc_sec_sys_buy_unit(self, portfolio, sec):
        
        si = self.sec_info.data[sec]
        """ 
        股票账户且不支持融资，针对融资账户 TODO
        简单使用单证券最大持仓为总持仓10%控制持仓单元
        """
        if(self.sec_info.sec_is_A(sec)):
            value = portfolio.total_value   # 当前持有的股票和现金的总价值
            
            sys_trade_unit = value / 10 / self.unit_limit / (si['prices']['close'])[-1]
            min_buy_unit = self.sec_info.sec_minimum_buy_unit(sec)
            return int( sys_trade_unit / min_buy_unit) * min_buy_unit

        # 期货交易单元算法
        elif self.sec_info.sec_is_future(sec):
            value = portfolio.total_value     # 保证金交易使用净资产
            dollar_volatility = si['dollars_per_share'] * (si['Ns'])[-1]
            sys_trade_unit = int(value * self.volatility_by_value / dollar_volatility)
            if sys_trade_unit <= 0:
                raise Exception('钱太少')

            return sys_trade_unit

        raise ValueError("不支持的证券类型")

    
    def futures_contracts_rollover(self):
        """期货调仓"""

        orders = []
        for sec in self.securities_trade_info:
            sti = self.securities_trade_info[sec]
            if self.sec_info.is_maincontract(sec) == False:
                # 移仓 [sec, -position, 'sell_for_cash', sec_info_data[sec]['current_price'], 'long', self.pindex]
                # next_contr = self.sec_info.next_contract(sec)
                orders = []
                if sti['position'] > 0:
                    orders.append([sec, -sti['position'], 'close4_contract_rollover',0, 'long', self.pindex])
                    # orders.append([next_contr, sti['position'], 'open4_contract_rollover',0, 'long', self.pindex])
                elif sti['short_position'] > 0:
                    orders.append([sec, -sti['short_position'], 'close4_contract_rollover',0, 'short', self.pindex])
                    # orders.append([next_contr, sti['short_position'], 'buy4_contract_rollover',0, 'short', self.pindex])

        return orders


    def manage_cash(self, portfolio, securities_data, current_price_attr = 'last_price'):
        """ 现金管理, 买入银华日利，应在交易日最后一分钟运行 """

        orders = []
        cash_available = portfolio.available_cash

        sec = self.money_market_fund['symbol']
        current_price = 0
        if current_price_attr == 'last_price':
            current_price = securities_data[sec].last_price
        elif current_price_attr == 'price':
            current_price = securities_data[sec].price
        money_fund_shares = math.floor(cash_available / current_price / 100 ) * 100
        if money_fund_shares > 0:
            orders.append([sec, money_fund_shares, '', 100, 'long', self.pindex])

        return orders

"""
海龟系统1 主要是入市检测
"""
class Sys1TradeSystem(BaseTradeSystem):

    #设置海龟交易策略参数
    def set_trade_config(self):
        """ 
        入场离场信号参数，跟 Donchian 通道相关
        """
        self.trade_config = {
            # 系统1入市的第一信号trailing date
            'enter_days': 20,
            # 系统1 exiting market trailing date
            'exit_days': 10,
            # 系统1入市的第二信号trailing date
            'enter_days2': 55
        }
    
    # 入市：决定系统1、系统2是否应该入市，更新系统1和系统2的突破价格
    # 海龟将所有资金分为2部分：一部分资金按系统1执行，一部分资金按系统2执行
    # 输入：当前价格-float, 现金-float, 天数-int
    # 输出：none
    def market_in(self, security, current_price):
        signal = None
        # Build position if current price is higher than highest in past
        
        
        sti = self.securities_trade_info[security]
        if sti['in_buy_list'] == False:
            return None

        enter_days = self.trade_config['enter_days']
        enter_days2 = self.trade_config['enter_days2']

        si = self.sec_info.data[security]

        if sti['sys1_first_break_flag'] != 0:#之前已发生突破
            if sti['sys1_loss_flag']:#且发生（不一定实际）止损
                # 2次突破
                if current_price > sti['price_highest_'+str(enter_days)]: # 突破Donchian 通道上沿
                    sti['sys1_first_break_flag'] = 0
                    signal = [security, sti['unit'], 'market_in', current_price, 'long']
                
                if si['shortable']:
                    if current_price < sti['price_lowest_'+str(enter_days)]: # 突破Donchian 通道下沿
                        sti['sys1_first_break_flag'] = 0
                        signal = [security, sti['unit'], 'market_in', current_price, 'short']
            else:
                """ 系统1突破后未出现止损，等待 55 日继续上突 """
                if current_price > sti['price_highest_'+str(enter_days2)]: # 突破Donchian 通道上沿
                    signal = [security, sti['unit'], 'market_in', current_price, 'long']

                if si['shortable']:
                    if current_price < sti['price_lowest_'+str(enter_days2)]: # 突破Donchian 通道下沿
                        signal = [security, sti['unit'], 'market_in', current_price, 'short']
                
                # 或者出现止损
                if sti['sys1_first_break_flag'] == 1: #一突上
                    break_price = sti['break_price']
                    # If the price has decreased by 2N, then clear all position
                    if current_price < (break_price - 2*(si['Ns'])[-1]):
                        sti['sys1_loss_flag'] = True
                elif (sti['sys1_first_break_flag'] == -1) and si['shortable']: # 一突下
                    break_price = sti['break_price']
                    # If the price has decreased by 2N, then clear all position
                    if current_price > (break_price + 2*(si['Ns'])[-1]):
                        sti['sys1_loss_flag'] = True
            
            sti['break_price'] = current_price 
        else:#待首次突破
            if current_price > sti['price_highest_'+str(enter_days)]: # 突破Donchian 通道上沿
                sti['sys1_first_break_flag'] = 1
                sti['break_price'] = current_price

            if current_price > sti['price_lowest_'+str(enter_days)]: # 突破Donchian 通道上沿
                sti['sys1_first_break_flag'] = -1
                sti['break_price'] = current_price
        
        return signal

    
    def after_order_success(self, _order, order_type):
        """ 子系统1 的订单成功 """
        
        security = _order.security
        super().after_order_success(_order, order_type)
        if order_type == 'market_in':
            sti = self.securities_trade_info[security]
            sti['sys1_loss_flag'] = False

    """
    系统1止损
    """
    def stop_loss(self, security, current_price):
        sti = self.securities_trade_info[security]
        sti['sys1_loss_flag'] = True

        return super().stop_loss(security, current_price)  #仅 python 3
        
    """ 
    更新本系统的各证券基本交易单位 
    """
    def update_sec_trade_info(self, portfolio, securities_data):

        super().update_sec_trade_info(portfolio, securities_data)

        enter_days2 = self.trade_config['enter_days2']

        sis = self.sec_info.data
        for sec in sis:
            sti = self.securities_trade_info[sec]
            if 'sys1_loss_flag' not in sti.keys():
                sti['sys1_loss_flag'] = False

            if 'sys1_first_break_flag' not in sti.keys():
                sti['sys1_first_break_flag'] = 0

            sti['price_highest_'+str(enter_days2)] = max(sis[sec]['prices']['high'][-enter_days2:])
            sti['price_lowest_'+str(enter_days2)] = min(sis[sec]['prices']['high'][-enter_days2:])
        
        pass
